/**
 * Alipay.com Inc.
 * Copyright (c) 2004-2023 All Rights Reserved.
 */
package com.example.spider.proxy.core.distinct;

import org.redisson.api.RedissonClient;

/**
 * 暂时不要过早优化
 * 1. 使用 Set<IP:port>
 * 2. 使用 Map<IP(1.2), Base64(IP(3.4):port)>
 * 3. 进一步考虑BitSet压缩
 *
 * 去重结构：
 * ip：xx1.xx2.xx3.xx4:port
 * key 选取：范围不能太广，个数不应该太多，理想在几百个，ip前两段代表地理位置，最多65536个，且代理一般只要部分地区代理，不会特别多。
 * value 选取：ip、port均为数字，且变化范围有限，优先考虑 bitSet，但直接使用存储所有范围太占内存，资源浪费严重。
 *    公共代理业务现状：一个ip下，不会有多个端口，而且绝大多数代理端口号就那么不到64个，不再该范围内的走单独压缩协议，如：Set<ip:port>
 * 最终
 *    xx1.xx2代表地址作为分段key，xx3.xx4 + port -> 24bit (2M空间)
 *    --------------------------------------
 * 去重目的：避免重复爬、存、测试
 *
 *    --------------------------------------
 * 数据支撑：爬取了4w个代理，发现port大概有7000个，出现频次>3的端口号仅有400个，出现频次 >10的仅有100个，出现频次>18的仅有60个
 * @see IpPortDistributeAnalyzer
 * http://www.5ixuexiwang.com/html/diannaojiaocheng/rumen/1944.html
 * 提到的，但不在前64的：telnet 23、FTP21  weblogic7001、9080
 * 保存port位数、覆盖多少的port、需要占用内存
 * 4 ----- 49% -----128Kb
 * 8 ----- 62% -----256Kb
 * 16 ----- 68% -----512Kb【建议最小16】
 * 32 ----- 72% ----- 1M
 * 64 ----- 75% ----- 2M
 * 128 ----- 77% ----- 4M 【成本高】
 * 256 ----- 78% ----- 8M
 * -----
 * 成本 = ip数据量 * （100% - 命中比例）* 重试成本 * 波动系数（4w数据代表爬取的数据port占比？） + 内存 * 数据量 * ip分布系数
 * -----
 * AtomicInteger[65536]
 * AtomicInteger[port - 1]++
 *
 * ZINCRBY key increment port
 */
public class RedisIpPortDistincter implements IpPortDistincter {

    private final RedissonClient       redissonClient;

    //private final LockOnWriteCacheMap lockOnWriteCacheMap;

    public RedisIpPortDistincter(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Override
    public boolean add(String ip, int port) {
        return redissonClient.getSet("SPD:proxy:distinctSet").add(ip + ":" + port);
//        return getOrAdd(ip, port, true);
    }

    @Override
    public boolean contains(String ip, int port) {
        return redissonClient.getSet("SPD:proxy:distinctSet").contains(ip + ":" + port);
//        return getOrAdd(ip, port, false);
    }


    // ----------------------- 不要过早进一步优化 ---------------------------------

    /**
     * 操作
     * @param ip ip
     * @param port 端口
     * @param add 操作
     * @return
     *  add：之前有返回false，之前没有返回true，表示是否新增元素
     *  !add：返回是否存在
     */
//    private boolean getOrAdd(String ip, int port, boolean add) {
//        String[] ipParts = ip.split("\\.");
//        String prefix = ipParts[0] + ensure3Digest(ipParts[1]);
//        String key = prefix + "_" + port;
//        int suffixToInt = Integer.parseInt(ipParts[2]) << 8 | Integer.parseInt(ensure3Digest(ipParts[3]));
//
//        RBitSet bs = redissonClient.getBitSet("app_ipport_distinct_" + key);
//        boolean exist = bs.get(suffixToInt);
//        if (add) {
//            if (exist) {
//                return false;
//            } else {
//                bs.set(suffixToInt);
//                return true;
//            }
//        } else {
//            return exist;
//        }
//    }
//
//    private static String ensure3Digest(String part) {
//        int length = part.length();
//        if (length == 3) {
//            return part;
//        }
//        return "0".repeat(3 - length) + part;
//    }
//
//    /**
//     * 将 ip 字符串转换为 int 类型的数字
//     * <p>
//     * 思路就是将 ip 的每一段数字转为 8 位二进制数，并将它们放在结果的适当位置上
//     *
//     * @param ipString ip字符串，如 127.0.0.1
//     * @return ip字符串对应的 int 值
//     */
//    public static int ip2Int(String ipString) {
//        // 取 ip 的各段
//        String[] ipSlices = ipString.split("\\.");
//        int rs = 0;
//        for (int i = 0; i < ipSlices.length; i++) {
//            // 将 ip 的每一段解析为 int，并根据位置左移 8 位
//            int intSlice = Integer.parseInt(ipSlices[i]) << 8 * i;
//            // 或运算
//            rs = rs | intSlice;
//        }
//        return rs;
//    }
//
//    /**
//     * 将 int 转换为 ip 字符串
//     *
//     * @param ipInt 用 int 表示的 ip 值
//     * @return ip字符串，如 127.0.0.1
//     */
//    public static String int2Ip(int ipInt) {
//        String[] ipString = new String[4];
//        for (int i = 0; i < 4; i++) {
//            // 每 8 位为一段，这里取当前要处理的最高位的位置
//            int pos = i * 8;
//            // 取当前处理的 ip 段的值
//            int and = ipInt & (255 << pos);
//            // 将当前 ip 段转换为 0 ~ 255 的数字，注意这里必须使用无符号右移
//            ipString[i] = String.valueOf(and >>> pos);
//        }
//        return String.join(".", ipString);
//    }

}